1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 /** 12 * This file provides the essential information for specifying vertices 13 * for the target 3D API. Its Attributes/Layout, some preset layouts. 14 * The workflow for vertices are entirely based on OpenGL, using VAOs and VBOs 15 * 16 */ 17 18 module hip.hiprenderer.vertex; 19 import hip.hiprenderer.renderer; 20 import hip.error.handler; 21 import hip.console.log; 22 public import hip.hiprenderer.backend.gl.glvertex; 23 // version(Android){alias index_t = ushort;} 24 // else{alias index_t = uint;} 25 alias index_t = ushort; 26 27 28 index_t index_t_maxQuads() 29 { 30 return cast(index_t)(ushort.max / 6); 31 } 32 index_t index_t_maxQuadIndices() 33 { 34 return cast(index_t)(index_t_maxQuads * 6); 35 } 36 37 38 39 enum InternalVertexAttribute 40 { 41 POSITION = 0, 42 TEXTURE_COORDS, 43 COLOR 44 } 45 46 enum InternalVertexAttributeFlags 47 { 48 POSITION = 1 << InternalVertexAttribute.POSITION, 49 TEXTURE_COORDS = 1 << InternalVertexAttribute.TEXTURE_COORDS, 50 COLOR = 1 << InternalVertexAttribute.COLOR, 51 } 52 enum HipBufferUsage 53 { 54 DYNAMIC, 55 STATIC, 56 DEFAULT 57 } 58 59 enum HipAttributeType 60 { 61 ///Used as a unsigned r8g8b8a8 normalized type. 62 Rgba32, 63 Float, 64 Uint, 65 Int, 66 Bool 67 } 68 69 70 struct HipVertexAttributeInfo 71 { 72 uint index; 73 uint count; 74 uint offset; 75 uint typeSize; 76 HipAttributeType valueType; 77 string name; 78 } 79 80 81 interface IHipVertexBufferImpl 82 { 83 void bind(); 84 void unbind(); 85 void setData(const void[] data); 86 void updateData(int offset, const void[] data); 87 } 88 interface IHipIndexBufferImpl 89 { 90 void bind(); 91 void unbind(); 92 void setData(const index_t[] data); 93 void updateData(int offset, const index_t[] data); 94 } 95 interface IHipVertexArrayImpl 96 { 97 void bind(IHipVertexBufferImpl vbo, IHipIndexBufferImpl ebo); 98 void unbind(IHipVertexBufferImpl vbo, IHipIndexBufferImpl ebo); 99 void setAttributeInfo(ref HipVertexAttributeInfo info, uint stride); 100 ///Was created because Direct3D 11 needs shader to create its VAO 101 void createInputLayout(Shader s); 102 } 103 104 105 /** 106 * For using this class, you must first define the vertex layout for after that, create the vertex 107 * buffer and/or the index buffer. 108 */ 109 class HipVertexArrayObject 110 { 111 IHipVertexArrayImpl VAO; 112 IHipVertexBufferImpl VBO; 113 IHipIndexBufferImpl EBO; 114 ///Accumulated size of the vertex data 115 uint stride; 116 ///How many data slots it uses, for instance, vec3 will count +3 117 uint dataCount; 118 HipVertexAttributeInfo[] infos; 119 120 protected bool isBonded; 121 protected bool hasVertexInitialized; 122 protected bool hasIndexInitialized; 123 124 /** 125 * Remember calling sendAttributes 126 */ 127 this() 128 { 129 isBonded = false; 130 this.VAO = HipRenderer.createVertexArray(); 131 } 132 133 /** 134 * Populates a buffer with indices forming quads 135 * Returns if the output can contain the size 136 */ 137 static bool putQuadBatchIndices(ref index_t[] output, size_t countQuads) 138 { 139 assert(output.length >= countQuads*6, "Out of bounds"); 140 if(output.length < countQuads*6) 141 return false; 142 index_t index = 0; 143 for(index_t i = 0; i < countQuads; i++) 144 { 145 output[index+0] = cast(index_t)(i*4+0); 146 output[index+1] = cast(index_t)(i*4+1); 147 output[index+2] = cast(index_t)(i*4+2); 148 149 output[index+3] = cast(index_t)(i*4+2); 150 output[index+4] = cast(index_t)(i*4+3); 151 output[index+5] = cast(index_t)(i*4+0); 152 index+=6; 153 } 154 return true; 155 } 156 157 /** 158 * Creates and binds an index buffer. 159 */ 160 void createIndexBuffer(index_t count, HipBufferUsage usage) 161 { 162 this.EBO = HipRenderer.createIndexBuffer(count, usage); 163 this.bind(); 164 this.EBO.bind(); 165 } 166 /** 167 * Creates and binds a vertex buffer. 168 * 169 * The vertex buffer size is dependant on the attributes that were appended to this vertex array. 170 */ 171 void createVertexBuffer(uint count, HipBufferUsage usage) 172 { 173 this.VBO = HipRenderer.createVertexBuffer(count*this.stride, usage); 174 this.bind(); 175 this.VBO.bind(); 176 } 177 /** 178 * This function creates an attribute information, 179 * for later sending it(it is necessary as the stride needs to be recalculated) 180 */ 181 HipVertexArrayObject appendAttribute( 182 uint count, 183 HipAttributeType valueType, 184 uint typeSize, 185 string infoName, 186 bool isPadding = false, 187 ) 188 { 189 HipVertexAttributeInfo info; 190 info.name = infoName; 191 info.count = count; 192 info.valueType = valueType; 193 info.typeSize = typeSize; 194 info.index = cast(uint)infos.length; 195 //It actually is the `last stride`, which is the same as the offset is the total current stride 196 info.offset = stride; 197 // if(!isPadding) 198 { 199 infos~= info; 200 dataCount+= count; 201 } 202 stride+= count*typeSize; 203 return this; 204 } 205 206 HipVertexArrayObject appendAttribute(T)(string infoName, bool isPadding = false) 207 { 208 uint count = 1; 209 HipAttributeType type = HipAttributeType.Float; 210 uint typeSize = float.sizeof; 211 import hip.math.vector; 212 213 static if(is(T == Vector2)) count = 2; 214 else static if(is(T == Vector3)) count = 3; 215 else static if(is(T == Vector4) || is(T == HipColorf)) count = 4; 216 else static if(is(T == HipColor)) 217 { 218 type = HipAttributeType.Rgba32; 219 count = 4; 220 typeSize = ubyte.sizeof; 221 } 222 else 223 { 224 static if(is(T == int)) type = HipAttributeType.Int; 225 else static if(is(T == uint)) type = HipAttributeType.Uint; 226 else static if(is(T == bool)) type = HipAttributeType.Bool; 227 else 228 static assert(is(T == float), "Unrecognized type for attribute: "~T.stringof); 229 230 typeSize = T.sizeof; 231 } 232 return appendAttribute(count, type, typeSize ,infoName, isPadding); 233 } 234 235 /** 236 * Sets the attribute infos that were appended to this object. This function must only be called 237 * after binding/creating a VBO, or it will fail 238 */ 239 void sendAttributes(Shader s) 240 { 241 if(!isBonded) 242 { 243 ErrorHandler.showErrorMessage("VertexArrayObject error", "VAO wasn't bound when trying to send its attributes"); 244 return; 245 } 246 foreach(info; infos) 247 this.VAO.setAttributeInfo(info, stride); 248 this.VAO.createInputLayout(s); 249 } 250 251 void bind() 252 { 253 // if(!this.isBonded) 254 // { 255 isBonded = true; 256 this.VAO.bind(this.VBO, this.EBO); 257 // } 258 } 259 void unbind() 260 { 261 // if(this.isBonded) 262 // { 263 isBonded = false; 264 this.VAO.unbind(this.VBO, this.EBO); 265 // } 266 } 267 268 /** 269 * Sets the VBO data. Use this function only for initialization as it allocates memory. 270 * 271 * If you wish to only update its data, call updateVertices instead. 272 */ 273 void setVertices(const void[] data) 274 { 275 if(VBO is null) 276 ErrorHandler.showErrorMessage("Null VertexBuffer", "No vertex buffer was created before setting its vertices"); 277 else 278 { 279 hasVertexInitialized = true; 280 this.bind(); 281 this.VBO.setData(data); 282 } 283 } 284 /** 285 * Update the VBO. Won't cause memory allocation. 286 * Params: 287 * count = How many vertices to update 288 * data = The data containing a type which is conforming to the VAO. 289 * offset = The offset is always multiplied by this vertex array object stride. 290 */ 291 void updateVertices(const void[] data, int offset = 0) 292 { 293 if(VBO is null) 294 ErrorHandler.showErrorMessage("Null VertexBuffer", "No vertex buffer was created before setting its vertices"); 295 ErrorHandler.assertExit(hasVertexInitialized, "Vertex must setData before updating its contents."); 296 this.bind(); 297 this.VBO.updateData(offset*this.stride, data); 298 } 299 /** 300 * Will set the indices data. Beware that this function may allocate memory. 301 * 302 * If you need to only change its data value instead of allocating memory for a greater index buffer 303 * call updateIndices 304 */ 305 void setIndices(const index_t[] data) 306 { 307 if(EBO is null) 308 ErrorHandler.showErrorMessage("Null IndexBuffer", "No index buffer was created before setting its indices"); 309 else 310 { 311 hasIndexInitialized = true; 312 this.bind(); 313 this.EBO.setData(data); 314 } 315 } 316 /** 317 * Updates the index buffer's data. It won't allocate memory 318 */ 319 void updateIndices(const index_t[] data, int offset = 0) 320 { 321 if(EBO is null) 322 ErrorHandler.showErrorMessage("Null IndexBuffer", "No index buffer was created before setting its indices"); 323 else 324 { 325 ErrorHandler.assertExit(hasIndexInitialized, "Index must setData before updating its contents."); 326 this.bind(); 327 this.EBO.updateData(cast(int)(offset*index_t.sizeof), data); 328 } 329 } 330 331 /** 332 * Receives a struct and creates a VAO based on its member types and names. 333 */ 334 static HipVertexArrayObject getVAO(T)() if(is(T == struct)) 335 { 336 import std.traits:isFunction; 337 import hip.util.reflection:hasUDA; 338 339 HipVertexArrayObject obj = new HipVertexArrayObject(); 340 static foreach(member; __traits(allMembers, T)) 341 {{ 342 alias mem = __traits(getMember, T, member); 343 static if(!isFunction!(mem) && __traits(compiles, mem.offsetof)) 344 { 345 obj.appendAttribute!((typeof(mem))) 346 ( 347 member, 348 hasUDA!(mem, HipShaderInputPadding) 349 ); 350 } 351 }} 352 return obj; 353 } 354 355 }